import cv2 as cv
import numpy as np
import time
import math as m
import numba

def img_zoom(img, zm):
   """对于传入的图像进行缩放操作, *zm:缩放因子"""
   time1 = time.time()
   rows, cols = img.shape[:2]  #获取宽和高
   new_img = np.zeros(img.shape, dtype=np.uint8) #新建同原图大小一致的空图像
   # 手写图像缩放代码
   # # 计算新图像尺寸
   # new_rows = max(1, int(rows * zm))
   # new_cols = max(1, int(cols * zm))
   #
   # # 创建缩放后的图像
   # if len(img.shape) == 3:  # 彩色图像
   #     scaled_img = np.zeros((new_rows, new_cols, 3), dtype=np.uint8)
   # else:  # 灰度图像
   #     scaled_img = np.zeros((new_rows, new_cols), dtype=np.uint8)
   #
   # # 计算缩放比例
   # row_ratio = rows / new_rows if new_rows > 0 else 0
   # col_ratio = cols / new_cols if new_cols > 0 else 0
   #
   # # 遍历新图像的每个像素（最近邻插值）
   # for i in range(new_rows):
   #     for j in range(new_cols):
   #         # 计算在原图像中对应的位置（最近邻）
   #         src_i = int(i * row_ratio)
   #         src_j = int(j * col_ratio)
   #
   #         # 确保不越界
   #         src_i = min(src_i, rows - 1)
   #         src_j = min(src_j, cols - 1)
   #
   #         # 复制像素值
   #         scaled_img[i, j] = img[src_i, src_j]
   #
   # # 根据缩放因子处理最终结果
   # if zm > 1:  # 放大：裁剪到原图像大小
   #     # 确保裁剪范围不超过原图尺寸
   #     crop_rows = min(rows, scaled_img.shape[0])
   #     crop_cols = min(cols, scaled_img.shape[1])
   #     result = scaled_img[0:crop_rows, 0:crop_cols]
   # else:  # 缩小：将缩放后的图像放在左上角，其余部分为黑色
   #     # 创建与原图相同大小的图像
   #     if len(img.shape) == 3:  # 彩色图像
   #         result = np.zeros((rows, cols, 3), dtype=np.uint8)
   #     else:  # 灰度图像
   #         result = np.zeros((rows, cols), dtype=np.uint8)
   #
   #     # 确保放置范围不超过目标图像尺寸
   #     place_rows = min(rows, scaled_img.shape[0])
   #     place_cols = min(cols, scaled_img.shape[1])
   #     result[0:place_rows, 0:place_cols] = scaled_img[0:place_rows, 0:place_cols]
   #
   #
   # time2 = time.time()
   # print("手写缩放程序处理时间： %.3f毫秒" % ((time2 - time1) * 1000))
   # return result

   # opencv图像缩放
   img = cv.resize(img, (int(cols*zm), int(rows *zm)), interpolation=cv.INTER_CUBIC)
   if zm>1: # 原图像大小显示
      new_img = img[0:cols, 0:rows]
   else:
      new_img[0:img.shape[0], 0:img.shape[1]] = img
   time2 = time.time()
   print("opencv缩放程序处理时间：%.3f毫秒" %((time2 - time1) * 1000))
   return new_img

def img_translation(img, trans):
   """对于传入的图像进行左右, *trans:平移参数"""
   time1 = time.time()
   new_img = np.zeros(img.shape, dtype=np.uint8)  # 新建同原图大小一致的空图像
   # # 手写图像平移代码
   rows, cols = img.shape[:2]  # 获取宽和高
   if len(img.shape) == 3:  # 彩色图像
       new_img = np.zeros((rows, cols, 3), dtype=np.uint8)
   else:  # 灰度图像
       new_img = np.zeros((rows, cols), dtype=np.uint8)

       # 手写图像平移代码
   for i in range(rows):
       for j in range(cols):
           # 计算平移后的位置

           new_j = j + trans
           # 检查是否在图像范围内
           if 0 <= new_j < cols:
               new_img[i, new_j] = img[i, j]

   time2 = time.time()
   print("手写图像平移程序处理时间：%.3f毫秒" %((time2 - time1) * 1000))
   return new_img

def img_imgMirror(img):

   time1 = time.time()
   rows, cols = img.shape[:2]  # 获取宽和高
   new_img = np.zeros(img.shape, dtype=np.uint8)  # 新建同原图大小一致的空图像
   for col in range(cols):  # 遍历每一列
      new_img[:, col] = img[:, cols-1-col]
   time2 = time.time()
   print("手写镜面变换程序处理时间：%.3f毫秒" %((time2 - time1) * 1000))
   return new_img

def img_rotation(img, rot):
   """对于传入的图像进行旋转，可以绕任一点旋转, *rot:旋转角度"""
   rows, cols = img.shape[:2]  # 获取宽和高
   b, a = rows / 2, cols / 2  # 设置旋转点位置
   h, w = rows / 2, cols / 2  # 图像高宽的一半
   time1 = time.time()

   # # 手写图像绕任一点旋转代码
   # # 将角度转换为弧度
   # rad = m.radians(rot)
   # cos_rad = m.cos(rad)
   # sin_rad = m.sin(rad)
   #
   # # 计算旋转后图像的边界
   # # 计算四个角点旋转后的位置
   # corners = [
   #     (0 - a, 0 - b),  # 左上角
   #     (cols - a, 0 - b),  # 右上角
   #     (cols - a, rows - b),  # 右下角
   #     (0 - a, rows - b)  # 左下角
   # ]
   #
   # # 旋转四个角点
   # rotated_corners = []
   # for x, y in corners:
   #     new_x = x * cos_rad - y * sin_rad
   #     new_y = x * sin_rad + y * cos_rad
   #     rotated_corners.append((new_x, new_y))
   #
   # # 计算新图像的尺寸
   # x_coords = [point[0] for point in rotated_corners]
   # y_coords = [point[1] for point in rotated_corners]
   # min_x, max_x = min(x_coords), max(x_coords)
   # min_y, max_y = min(y_coords), max(y_coords)
   #
   # new_cols = int(max_x - min_x)
   # new_rows = int(max_y - min_y)
   #
   # # 创建新图像
   # if len(img.shape) == 3:  # 彩色图像
   #     new_img = np.zeros((new_rows, new_cols, 3), dtype=np.uint8)
   # else:  # 灰度图像
   #     new_img = np.zeros((new_rows, new_cols), dtype=np.uint8)
   #
   # # 新图像的中心点
   # new_a, new_b = new_cols / 2, new_rows / 2
   #
   # # 反向映射：遍历新图像的每个像素，找到在原图像中对应的位置
   # for i in range(new_rows):
   #     for j in range(new_cols):
   #         # 将新图像坐标转换为以中心为原点的坐标
   #         x = j - new_a
   #         y = i - new_b
   #
   #         # 反向旋转（逆变换）
   #         src_x = x * cos_rad + y * sin_rad + a
   #         src_y = -x * sin_rad + y * cos_rad + b
   #
   #         # 最近邻插值
   #         if 0 <= src_x < cols and 0 <= src_y < rows:
   #             src_i = int(src_y)
   #             src_j = int(src_x)
   #             new_img[i, j] = img[src_i, src_j]
   # time2 = time.time()
   # print("手写旋转程序处理时间：%.3f毫秒" %((time2 - time1) * 1000))

   # opencv绕任一点旋转代码
   # 第一个参数是旋转中心，第二个参数是旋转角度，第三个因子是旋转后的缩放因子
   M = cv.getRotationMatrix2D((a, b), rot, 1)
   cos, sin = np.abs(M[0, 0]), np.abs(M[0, 1])
   new_cols = int((rows * sin) + (cols * cos))
   new_rows = int((rows * cos) + (cols * sin))
   M[0, 2] += (new_cols / 2) - w
   M[1, 2] += (new_rows / 2) - h
   new_img = cv.warpAffine(img, M, (new_cols, new_rows))  # 第三个参数是输出图像的尺寸中心，图像的宽和高
   time2 = time.time()
   print("opencv旋转程序处理时间：%.3f毫秒" %((time2 - time1) * 1000))
   return new_img

def onmouse_pick_points(event, x, y, flags, l_ImgPot):
   """card_correction的鼠标回调函数, """
   if event == cv.EVENT_LBUTTONDOWN:
      print('x = %d, y = %d' % (x, y))
      l_ImgPot[2].append((x, y))
      cv.drawMarker(l_ImgPot[1], (x, y), (0, 0, 255))
   if event == cv.EVENT_RBUTTONDOWN:
      l_ImgPot[1] = l_ImgPot[0].copy() # 将没有画十字的原图重新赋值给显示图像
      if len(l_ImgPot[2]) != 0:
         l_ImgPot[2].pop() # 将最后一次绘制的标记清除
         for i in range(len(l_ImgPot[2])): # 重新绘制全部标记
            cv.drawMarker(l_ImgPot[1], l_ImgPot[2][i], (0, 0, 255))

def card_correction(img):
   """对于传入的图像进行鼠标交互，选择四个顶点进行名片矫正"""
   """对于传入的图像进行鼠标交互，选择四个顶点进行名片矫正"""
   time1 = time.time()

   # 创建图像副本和点列表
   l_ImgPot = [img, img.copy(), []]  # [0]原图 [1]处理后图 [2]点列表

   # 使用OpenCV窗口选择点
   cv.namedWindow('Select 4 Corners (Press SPACE when done, ESC to cancel)', cv.WINDOW_AUTOSIZE)
   cv.setMouseCallback('Select 4 Corners (Press SPACE when done, ESC to cancel)', onmouse_pick_points, l_ImgPot)

   print("请依次点击名片的四个角点（左上、右上、右下、左下）")
   print("右键点击可撤销上一个点，按空格键确认选择，按ESC退出")

   result_img = img.copy()  # 默认返回原图

   while True:
       cv.imshow('Select 4 Corners (Press SPACE when done, ESC to cancel)', l_ImgPot[1])
       key = cv.waitKey(1) & 0xFF

       # 按空格键确认选择并执行矫正
       if key == ord(' '):
           if len(l_ImgPot[2]) == 4:
               # 对四个点进行排序：左上、右上、右下、左下
               points = np.array(l_ImgPot[2], dtype=np.float32)

               # 计算点的中心
               center = np.mean(points, axis=0)

               # 根据点相对于中心的位置进行排序
               diff = points - center

               # 计算每个点的角度（atan2）
               angles = np.arctan2(diff[:, 1], diff[:, 0])

               # 按角度排序（从左上开始顺时针）
               sorted_indices = np.argsort(angles)
               sorted_points = points[sorted_indices]

               # 确保顺序正确：调整顺序为左上、右上、右下、左下
               # 重新排列以确保正确的顺序
               top_left = sorted_points[np.argmin(np.sum(sorted_points, axis=1))]
               bottom_right = sorted_points[np.argmax(np.sum(sorted_points, axis=1))]

               remaining = [p for p in sorted_points if
                            not np.array_equal(p, top_left) and not np.array_equal(p, bottom_right)]

               if len(remaining) == 2:
                   # 根据x坐标区分右上和左下
                   if remaining[0][0] > remaining[1][0]:
                       top_right, bottom_left = remaining[0], remaining[1]
                   else:
                       top_right, bottom_left = remaining[1], remaining[0]

                   # 最终排序的点
                   src_points = np.array([top_left, top_right, bottom_right, bottom_left], dtype=np.float32)

                   # 计算目标图像的宽度和高度
                   width_top = np.linalg.norm(top_right - top_left)
                   width_bottom = np.linalg.norm(bottom_right - bottom_left)
                   max_width = max(int(width_top), int(width_bottom))

                   height_left = np.linalg.norm(bottom_left - top_left)
                   height_right = np.linalg.norm(bottom_right - top_right)
                   max_height = max(int(height_left), int(height_right))

                   # 目标点（矫正后的矩形）
                   dst_points = np.array([
                       [0, 0],
                       [max_width - 1, 0],
                       [max_width - 1, max_height - 1],
                       [0, max_height - 1]
                   ], dtype=np.float32)

                   # 计算透视变换矩阵
                   matrix = cv.getPerspectiveTransform(src_points, dst_points)

                   # 应用透视变换
                   result_img = cv.warpPerspective(img, matrix, (max_width, max_height))

                   print("矫正完成！")
                   cv.destroyAllWindows()
                   break
               else:
                   print("错误：无法正确排序四个点")
           else:
               print("请先选择4个点（当前选择了%d个点）" % len(l_ImgPot[2]))

       # 按ESC键退出
       elif key == 27:
           print("取消矫正操作")
           cv.destroyAllWindows()
           break

   time2 = time.time()
   print("名片矫正处理时间：%.3f毫秒" % ((time2 - time1) * 1000))

   return result_img

